package service

import (
	"JudgeClient/compiler/judger"
	"JudgeClient/global"
	"JudgeClient/idl"
	"JudgeClient/utils"
	"bytes"
	"crypto/md5"
	"os"
	"path/filepath"
	"runtime"
	"strings"
)

type JudgeService struct {
	Sid           string
	RunConf       global.RunConfig
	ExePath       string
	MaxCpuTime    int
	MaxMemory     int
	SubmissionDir string
	ProblemId     string
}

func (js *JudgeService) Judge() (runResult idl.JudgeResult, err error) {

	maxR := runtime.GOMAXPROCS(runtime.NumCPU())
	runResult.Sid = js.Sid
	// load testcases files
	testFiles, err := loadTestCases(js.ProblemId)
	if err != nil {
		return idl.JudgeResult{Sid: js.Sid, Passed: []idl.RunResult{}, UnPassed: []idl.RunResult{}}, err
	}

	// build channel
	jobCh := make(chan idl.TestCase, 100)
	resCh := make(chan idl.RunResult, 100)

	// routine pool

	for w := 0; w < maxR; w++ {
		go worker(js, jobCh, resCh)
	}

	// add jobs

	for _, t := range testFiles {
		tc := idl.TestCase{
			TestInPath:      filepath.Join(global.Conf.Path.TestCaseDir, js.ProblemId, t+".in"),
			TestOutPath:     filepath.Join(global.Conf.Path.TestCaseDir, js.ProblemId, t+".out"),
			UserOutFilename: t + ".out",
		}
		jobCh <- tc
	}

	close(jobCh)

	for i := 0; i < len(testFiles); i++ {
		rs := <-resCh
		if rs.ResultCode == global.RUN_SUCCESS {
			runResult.Passed = append(runResult.Passed, rs)
		} else {
			runResult.UnPassed = append(runResult.UnPassed, rs)
		}
	}
	//TODO:处理执行结果
	err = nil

	return
}

func worker(jc *JudgeService, inCh <-chan idl.TestCase, outCh chan<- idl.RunResult) {
	for t := range inCh {
		outCh <- jc.JudgeOne(t.TestInPath, t.TestOutPath, t.UserOutFilename)
	}
}

func (js *JudgeService) JudgeOne(testInPath string, testOutPath string, userOutFilename string) idl.RunResult {
	commands := strings.Split(string(js.RunConf.Command), " ")
	userOutputPath := filepath.Join(js.SubmissionDir, userOutFilename) //TODO:从数据库取
	result, error := judger.Run(idl.RunConfig{
		MaxCpuTime:       js.MaxCpuTime,
		MaxMemory:        js.MaxMemory,
		MaxStack:         128 * 1024 * 1024,
		MaxOutPutSize:    1024 * 1024 * 1024,
		MaxRealTime:      js.MaxCpuTime * 3,
		MaxProcessNumber: global.Conf.Judge.Unlimited,
		ExePath:          commands[0],
		InputPath:        testInPath,
		OutputPath:       userOutputPath,
		ErrorPath:        userOutputPath,
		Args:             commands[1:],
		Env:              append(js.RunConf.Env, "PATH="+os.Getenv("PATH")),
		LogPath:          filepath.Join(global.Conf.Path.WorkDir, global.Conf.Path.CompileLog),
		SecCompRuleName:  js.RunConf.SeccompRule,
		Uid:              global.Conf.Judge.RunUserId,
		Gid:              global.Conf.Judge.RunGroupId,
	})

	if error != nil {
		return idl.RunResult{
			CpuTime:    result.CpuTime,
			RealTime:   result.RealTime,
			Memory:     result.Memory,
			ResultCode: -1,
			ErrorCode:  result.ErrorCode,
			Output:     "",
			Signal:     result.Signal,
		}
	}

	userOutput, res := compareOutput(testOutPath, userOutputPath)
	if !res {
		// set to wrong answer
		result.ResultCode = -1
	}

	runResult := idl.RunResult{
		CpuTime:    result.CpuTime,
		RealTime:   result.RealTime,
		Memory:     result.Memory,
		ResultCode: result.ResultCode,
		ErrorCode:  result.ErrorCode,
		Output:     userOutput,
		Signal:     result.Signal,
	}

	return runResult
}

func compareOutput(testOutFilePath string, runOutFilePath string) (output string, res bool) {
	testOut, _ := os.ReadFile(testOutFilePath)
	// Linux files has a line break at the end defaultly,trim it!
	trimed := bytes.TrimRight(testOut, "\n")
	testMD5 := md5.Sum(trimed)
	userOut, _ := os.ReadFile(runOutFilePath)
	trimed = bytes.TrimRight(userOut, "\n")
	userMD5 := md5.Sum(trimed)
	output = string(userOut)
	res = bool(testMD5 == userMD5)
	defer os.Remove(runOutFilePath)
	return
}

func loadTestCases(testcasePath string) (fileName []string, err error) {
	path := filepath.Join(global.Conf.Path.TestCaseDir, testcasePath)
	inFileNames, err := utils.GetFileNames(path, ".in")
	if err != nil {
		return []string{}, err
	}
	for _, fiName := range inFileNames {
		if _, e := os.Stat(filepath.Join(path, fiName+".out")); e == nil {
			fileName = append(fileName, fiName)
		}
	}
	return fileName, nil
}
